﻿using System;
using System.Linq;
using System.Security.Principal;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Web.Security;

namespace HashFoo.Rest.Server
{
    /// <summary>
    /// Wires in asp.net request authentication using the Http Basic Authentication protocal.
    /// </summary>
    /// <remarks>
    /// If the request has already been authenticated (if the <see cref="HttpContext.Current"/> User is not null)
    /// the request is ignored.  Otherwise, the module will parse the auth header if it is available and use the <see cref="MembershipProvider"/>
    /// to authenticate.
    /// 
    /// The <see cref="MembershipProvider"/> is by default pulled from the ASP MVC <see cref="DependencyResolver"/>.  If it is null, 
    /// the <see cref="AspDefaultMembershipProvider"/> that wraps the default ASP.NET membership and role providers is used.
    /// 
    /// Consumers need to add this module in the web.config -- there is not automatic registration of the module.  If this module were to 
    /// be refactored into its own assembly (probably not a bad idea), then automatic registration would make more sense, as a user would
    /// not reference the assembly unless they wanted to use the module.
    /// </remarks>
    public class HttpBasicAuthModule : IHttpModule
    {
        const string AuthHeaderKey = "authorization";

        /// <summary>
        /// Provides access to the applications membership data.
        /// </summary>
        public IMembershipProvider MembershipProvider { get; set; }

        /// <summary>
        /// Creates the default module -- optionally uses <see cref="DependencyResolver"/> to find all dependencies, otherwise
        /// falls back on hard coded defaults.
        /// </summary>
        public HttpBasicAuthModule() :
            this(DependencyResolver.Current.GetService<IMembershipProvider>() ?? new AspDefaultMembershipProvider())
        {
            
        }

        /// <summary>
        /// Creates the module -- allows dependencies to be explicitly injected.
        /// </summary>
        /// <param name="membershipProvider"></param>
        public HttpBasicAuthModule(IMembershipProvider membershipProvider)
        {
            MembershipProvider = membershipProvider;
        }

        /// <summary>
        /// Initializes a module and prepares it to handle requests.
        /// </summary>
        /// <param name="context">An <see cref="T:System.Web.HttpApplication"/> that provides access to the methods, properties, and events common to all application objects within an ASP.NET application </param>
        public void Init(HttpApplication context)
        {
            context.AuthenticateRequest += OnAuthenticateRequest;
        }

        void OnAuthenticateRequest(object sender, EventArgs eventArgs)
        {
            var context = HttpContext.Current;

            Authenticate(context);
        }

        void Authenticate(HttpContext httpContext)
        {
            if (httpContext.User != null && httpContext.User.Identity.IsAuthenticated) return;

            var headers = httpContext.Request.Headers;
            var isBasicAuthRequest = headers.AllKeys.Select(k => k.ToLowerInvariant()).Contains(AuthHeaderKey);
            if (!isBasicAuthRequest) return;

            var authHeader = headers[AuthHeaderKey];
            var credentials = ParseAuthHeader(authHeader);
            if (credentials == null) return;

            var username = credentials[0];
            var password = credentials[1];

            var isValidUser = MembershipProvider.ValidateUser(username, password);
            if (!isValidUser) return;

            // set the user for the context, in case anyone else wants it.
            httpContext.User = new GenericPrincipal(new GenericIdentity(username), MembershipProvider.GetRoles(username));
        }

        static string[] ParseAuthHeader(string authHeader)
        {
            // Check this is a Basic Auth header 
            if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Basic")) return null;

            // Pull out the Credentials with are seperated by ':' and Base64 encoded 
            var base64Credentials = authHeader.Substring(6);
            var credentials = Encoding.ASCII.GetString(Convert.FromBase64String(base64Credentials)).Split(new[] { ':' });

            if (credentials.Length != 2 || string.IsNullOrEmpty(credentials[0]) || string.IsNullOrEmpty(credentials[0])) return null;

            // Okay this is the credentials 
            return credentials;
        }

        /// <summary>
        /// Disposes of the resources (other than memory) used by the module that implements <see cref="T:System.Web.IHttpModule"/>.
        /// </summary>
        public void Dispose()
        {
            
        }
    }
}